/*
 * luaRcallback.c
 */

#include "luaRcallback.h"
#include "stdlib.h"

#define LUACALLBACKARRAYSIZE 10

static int luaR_c_init = 0;
static void (*callbacks[LUACALLBACKARRAYSIZE+1])(int, struct lua_arg_list*);
static int callbackarray_size = 0;
static int callback_return_count = 0;
static lua_State *callback_state = NULL;

/*
 * Registers the given C function as a Lua function with the given package name
 * and function name.
 */
int luaR_callback_register( const char *pkg_name, const char *fn_name,
    void (*fn)(int argc, struct lua_arg_list* argv) ) {
    if( callbackarray_size < LUACALLBACKARRAYSIZE ) {
        if( luaR_add_callback(pkg_name, fn_name, callbackarray_size) ) {
            /* TODO: error */
            return -1;
        }
        else {
            callbacks[callbackarray_size] = fn;
            callbackarray_size++;
            return 0;
        }
    }
    else {
        /* TODO: error */
        return -1;
    }
}

/*
 * The Lua hook to C functions registered through luaR_callback_register().
 * When a C callback is registered, it is given a function ID and a Lua function
 * which calls this callback with the given arguments and the function ID used
 * to create the Lua function. The arguments are converted into a Lua arg list
 * struct, and the C callback is called with the newly created arg list.
 */
int luaR_callback( lua_State *L ) {
    int argc;
    int func_id;
    struct lua_arg_list *args;
    struct lua_arg_list *new_arg;
    callback_return_count = 0;
    callback_state = L;
    argc = 0;
    args = NULL;
    while( lua_gettop(L) > 1 ) {
        if( lua_isboolean(L, lua_gettop(L)) ) {
            new_arg = (struct lua_arg_list*)malloc(sizeof(struct lua_arg_list));
            new_arg->type = luaR_boolean;
            new_arg->value.boolean = lua_toboolean(L, lua_gettop(L));
        }
        else if( lua_isnumber(L, lua_gettop(L)) ) {
            new_arg = (struct lua_arg_list*)malloc(sizeof(struct lua_arg_list));
            new_arg->type = luaR_number;
            new_arg->value.number = lua_tonumber(L, lua_gettop(L));
        }
        else if( lua_isstring(L, lua_gettop(L)) ) {
            new_arg = (struct lua_arg_list*)malloc(sizeof(struct lua_arg_list));
            new_arg->type = luaR_string;
            new_arg->value.string = lua_tostring(L, lua_gettop(L));
        }
        else {
            new_arg = (struct lua_arg_list*)malloc(sizeof(struct lua_arg_list));
            new_arg->type = luaR_nil;
            new_arg->value.nil = 0;
        }
        new_arg->next = args;
        args = new_arg;
        argc++;
        lua_pop(L, 1);
    }
    if( lua_gettop(L) != 1 ) {
        /* TODO: error */
        printf("luaR_callback received no Lua args!\n");
        callback_state = NULL;
        return 0;
    }
    else {
        if( lua_isnumber(L, 1) ) {
            func_id = (int)lua_tointeger(L, 1);
            lua_pop(L, 1);
            if( func_id < callbackarray_size && func_id >= 0 ) {
                (callbacks[func_id])(argc, args);
                callback_state = NULL;
                return callback_return_count;
            }
            else {
                /* TODO: error */
                printf("Bad func_id given to luaR_callback: %d!\n", func_id);
                callback_state = NULL;
                return 0;
            }
        }
        else {
            /* TODO: error */
            printf("Bad func_id given to luaR_callback (not a a number)!\n");
            callback_state = NULL;
            return 0;
        }
    }
}

/*
 * Frees the given arg list, freeing the memory of every entity in the list.
 */
int luaR_free_arg_list( struct lua_arg_list *list ) {
    struct lua_arg_list *node;
    while( list != NULL ) {
        node = list;
        list = list->next;
        free(node);
    }
    return 0;
}

/*
 * Frees the given arg.
 */
int luaR_free_arg( struct lua_arg_list *list ) {
    if( list != NULL ) {
        free(list);
    }
    return 0;
}

/*
 * Takes a Lua arg list and attempts to set the given int (passed as a pointer)
 * to the boolean value the first arg on the list represents. This function
 * will fail if the first arg is not a boolean.
 * Returns the next element in the arg list after freeing the first arg.
 */
struct lua_arg_list * luaR_arg_getboolean( struct lua_arg_list *list, int *value ) {
    struct lua_arg_list *next;
    if( list != NULL && list->type == luaR_boolean ) {
        if( value != NULL ) {
            *value = list->value.boolean;
        }
        next = list->next;
        luaR_free_arg(list);
        return next;
    }
    else {
        /* TODO: error */
        return list;
    }
}

/*
 * Takes a Lua arg list and attempts to set the given double (passed as a
 * pointer) to the number value the first arg on the list represents. This
 * function will fail if the first arg is not a number.
 * Returns the next element in the arg list after freeing the first arg.
 */
struct lua_arg_list * luaR_arg_getnumber( struct lua_arg_list *list, double *value ) {
    struct lua_arg_list *next;
    if( list != NULL && list->type == luaR_number ) {
        if( value != NULL ) {
            *value = list->value.number;
        }
        next = list->next;
        luaR_free_arg(list);
        return next;
    }
    else {
        /* TODO: error */
        return list;
    }
}

/*
 * Takes a Lua arg list and attempts to set the given integer (passed as a
 * pointer) to the number value the first arg on the list represents. This
 * function will fail if the first arg is not a number.
 * Returns the next element in the arg list after freeing the first arg.
 */
struct lua_arg_list * luaR_arg_getinteger( struct lua_arg_list *list, int *value ) {
    struct lua_arg_list *next;
    if( list != NULL && list->type == luaR_number ) {
        if( value != NULL ) {
            *value = (int)(list->value.number);
        }
        next = list->next;
        luaR_free_arg(list);
        return next;
    }
    else {
        /* TODO: error */
        return list;
    }
}

/*
 * Takes a Lua arg list and attempts to set the given char array (passed as a
 * pointer) to the string value the first arg on the list represents. This
 * function will fail if the first arg is not a string.
 * Returns the next element in the arg list after freeing the first arg.
 */
struct lua_arg_list * luaR_arg_getstring( struct lua_arg_list *list, const char **value ) {
    struct lua_arg_list *next;
    if( list != NULL && list->type == luaR_string ) {
        if( value != NULL ) {
            *value = list->value.string;
        }
        next = list->next;
        luaR_free_arg(list);
        return next;
    }
    else {
        /* TODO: error */
        return list;
    }
}

/*
 * Pushes the given integer onto the Lua stack to be returned.
 * Will fail if this function is not called within a call to luaR_callback.
 */
void luaR_arg_pushinteger( int value ) {
    if( callback_state == NULL ) {
        /* TODO: error */
        printf("luaR_arg_pushinteger call outside callback!\n");
    }
    else {
        lua_pushinteger(callback_state, value);
        callback_return_count++;
    }
}

/*
 * Initializes the Lua callback interface.
 * This should only be called once!
 */
int luaR_callback_initialize() {
    if( luaR_c_init ) {
        /* TODO: warning */
        return 1;
    }
    else {
        luaR_package_create();
        luaR_package_add("devCallback", luaR_callback);
        luaR_package_set("RG_builtin");
        luaR_c_init = 1;
        return 0;
    }
}
